using System;
using System.Collections.Generic;
using System.Linq;
using Urs.Core;
using Urs.Core.Caching;
using Urs.Core.Data;
using Urs.Data.Domain.Stores;
using Urs.Data.Domain.Users;
using Urs.Data;
using Urs.Data.Domain.Configuration;

namespace Urs.Services.Stores
{
    public partial class GoodsService : IGoodsService
    {
        #region Constants
        private const string PRODUCTS_BY_ID_KEY = "Urs.goods.id-{0}";
        private const string PRODUCT_KEY = "Urs.goods.";
        #endregion

        #region Fields
        private readonly IRepository<User> _userRepository;
        private readonly IRepository<GoodsCategory> _goodsCategoryRepository;
        private readonly IRepository<GoodsBrandMapping> _goodsBrandRepository;
        private readonly IRepository<Goods> _goodsRepository;
        private readonly IRepository<GoodsPicture> _goodsPictureRepository;
        private readonly IRepository<GoodsParameterMapping> _goodsParameterMappingRepository;
        private readonly IRepository<GoodsReview> _goodsReviewRepository;
        private readonly IRepository<GoodsTagMapping> _goodsTagGoodsMappingRepository;
        private readonly IGoodsSpecService _goodsSpecService;
        private readonly IGoodsSpecParser _goodsSpecParser;
        private readonly IGoodsTagService _goodsTagService;
        private readonly IDataProvider _dataProvider;
        private readonly IDbContext _dbContext;
        private readonly ICacheManager _cacheManager;
        private readonly IWorkContext _workContext;
        private readonly CommonSettings _commonSettings;
        private readonly StoreSettings _storeSettings;
        private readonly IRepository<GoodsSpecCombination> _goodsSpecCombinationRepository;
        #endregion

        #region Ctor

        public GoodsService(ICacheManager cacheManager,
            IRepository<User> userRepository,
            IRepository<GoodsCategory> goodsCategoryRepository,
            IRepository<GoodsBrandMapping> goodsBrandRepository,
            IRepository<Goods> goodsRepository,
            IRepository<GoodsPicture> goodsPictureRepository,
            IRepository<GoodsParameterMapping> goodsParameterMappingRepository,
            IRepository<GoodsReview> goodsReviewRepository,
            IRepository<GoodsTagMapping> goodsTagGoodsMappingRepository,
            IGoodsSpecService goodsSpecService,
            IGoodsSpecParser goodsSpecParser,
            IGoodsTagService goodsTagService,
            IDataProvider dataProvider, IDbContext dbContext,
            IWorkContext workContext,
             CommonSettings commonSettings,
            StoreSettings storeSettings,
            IRepository<GoodsSpecCombination> goodsSpecCombinationRepository)
        {
            this._cacheManager = cacheManager;
            this._goodsRepository = goodsRepository;
            this._userRepository = userRepository;
            this._goodsCategoryRepository = goodsCategoryRepository;
            this._goodsBrandRepository = goodsBrandRepository;
            this._goodsPictureRepository = goodsPictureRepository;
            this._goodsParameterMappingRepository = goodsParameterMappingRepository;
            this._goodsReviewRepository = goodsReviewRepository;
            this._goodsSpecService = goodsSpecService;
            this._goodsSpecParser = goodsSpecParser;
            this._goodsTagService = goodsTagService;
            this._goodsTagGoodsMappingRepository = goodsTagGoodsMappingRepository;
            this._dataProvider = dataProvider;
            this._dbContext = dbContext;
            this._workContext = workContext;
            this._commonSettings = commonSettings;
            this._storeSettings = storeSettings;
            this._goodsSpecCombinationRepository = goodsSpecCombinationRepository;
        }

        #endregion

        #region Methods

        #region Goodss

        public virtual void DeleteGoods(Goods goods)
        {
            if (goods == null)
                throw new ArgumentNullException("goods");

            goods.Deleted = true;
            UpdateGoods(goods);
        }

        public virtual IList<Goods> GetSpecialGoodss(int takeCount = 0)
        {
            var query = _goodsRepository.Table;

            query = query.Where(q => q.SpecialPrice.HasValue && q.Published && !q.Deleted);

            var now = DateTime.Now;

            query = query.Where(q => q.SpecialStartTime.HasValue && q.SpecialStartTime.Value < now);

            query = query.Where(q => q.SpecialEndTime.HasValue && q.SpecialEndTime.Value > now);

            if (takeCount > 0)
                query = query.Take(takeCount);

            query = query.OrderByDescending(q => q.SaleVolume);
            return query.ToList();
        }

        public virtual IList<Goods> GetAllGoodssDisplayedOnHomePage()
        {
            var query = from p in _goodsRepository.Table
                        orderby p.Name
                        where !p.Deleted &&
                        p.ShowOnHomePage
                        select p;
            var list = query.ToList();
            return list;
        }

        public virtual Goods GetGoodsById(int goodsId)
        {
            if (goodsId == 0)
                return null;

            var goods = _goodsRepository.GetById(goodsId);
            return goods;
        }

        public virtual IList<Goods> GetGoodssByIds(int[] goodsIds, bool showHidden = true)
        {
            if (goodsIds == null || goodsIds.Length == 0)
                return new List<Goods>();

            var query = _goodsRepository.Table;

            if (!showHidden)
                query = query.Where(q => q.Published == true);

            query = query.Where(pv => goodsIds.Contains(pv.Id));
            query = query.OrderBy(pv => pv.Id);

            var list = query.ToList();
            return list;
        }

        public virtual IList<Goods> GetFeaturedGoodsByCategoryId(int categoryId, int number)
        {
            if (categoryId == 0)
                return new List<Goods>();
            var query = (from p in _goodsRepository.Table
                         join pc in _goodsCategoryRepository.Table on p.Id equals pc.GoodsId
                         where p.Published && categoryId == pc.CategoryId &&
                         !p.Deleted
                         select p);

            if (_storeSettings.FeaturedGoodsOrderBy == FeaturedGoodsOrderBy.CreateTime)
            {
                query = query.OrderByDescending(q => q.CreateTime);
            }
            else
            {
                query = query.OrderByDescending(q => q.ApprovedTotalReviews);
            }
            var list = query.Take(number).ToList();
            return list;
        }

        public virtual void InsertGoods(Goods goods)
        {
            if (goods == null)
                throw new ArgumentNullException("goods");

            _goodsRepository.Insert(goods);

            _cacheManager.RemoveByPattern(PRODUCT_KEY);
        }

        public virtual void UpdateGoods(Goods goods)
        {
            if (goods == null)
                throw new ArgumentNullException("goods");

            _goodsRepository.Update(goods);

            _cacheManager.RemoveByPattern(PRODUCT_KEY);
        }


        public virtual int GetCategoryGoodsNumber(IList<int> categoryIds = null)
        {
            if (categoryIds != null && categoryIds.Contains(0))
                categoryIds.Remove(0);

            var query = _goodsRepository.Table;
            query = query.Where(p => !p.Deleted && p.Published);

            query = query.Where(p => categoryIds.Contains(p.CategoryId));
            var result = query.Select(p => p.Id).Distinct().Count();
            return result;
        }

        public virtual IPagedList<Goods> SearchGoodss(out IList<int> filterableGoodsParameterOptionIds, bool loadFilterableGoodsParameterOptionIds = false,
            int[] categoryId = null, int brandId = 0, int creatorId = 0, bool? featuredGoodss = null,
            decimal? priceMin = null, decimal? priceMax = null, int goodsTagId = 0,
            string keywords = null, bool searchDescriptions = false, bool searchGoodsTags = false,
            IList<int> filteredSpecs = null, GoodsSortingEnum orderBy = GoodsSortingEnum.Position,
            int pageIndex = 0, int pageSize = int.MaxValue, bool showHidden = false, string sku = null)
        {
            filterableGoodsParameterOptionIds = new List<int>();

            #region search 
            var query = _goodsRepository.Table;
            query = query.Where(p => !p.Deleted);

            if (!showHidden)
            {
                query = query.Where(p => p.Published);
            }
            if (!string.IsNullOrEmpty(sku))
                query = query.Where(p => p.Sku.Contains(sku));
            if (!String.IsNullOrWhiteSpace(keywords))
            {
                query = from p in query
                        from pt in p.GoodsTags.DefaultIfEmpty()
                        from skus in p.GoodsSpecCombinations.DefaultIfEmpty()
                        where (p.Name.Contains(keywords)) || (p.Sku.Contains(keywords)) ||
                              (searchDescriptions && p.ShortDescription.Contains(keywords)) ||
                              (searchDescriptions && p.FullDescription.Contains(keywords)) ||
                              (searchGoodsTags && pt.GoodsTag.Name.Contains(keywords)) ||
                              skus.Sku.Contains(keywords) ||
                              pt.GoodsTag.Name.Contains(keywords)
                        select p;
            }
            var nowUtc = DateTime.Now;
            query = from pv in query
                    where
                        (
                            !priceMin.HasValue
                            ||
                            ((pv.SpecialPrice.HasValue && ((!pv.SpecialStartTime.HasValue || pv.SpecialStartTime.Value < nowUtc) && (!pv.SpecialEndTime.HasValue || pv.SpecialEndTime.Value > nowUtc))) && (pv.SpecialPrice >= priceMin.Value))
                            ||
                            ((!pv.SpecialPrice.HasValue || ((pv.SpecialStartTime.HasValue && pv.SpecialStartTime.Value > nowUtc) || (pv.SpecialEndTime.HasValue && pv.SpecialEndTime.Value < nowUtc))) && (pv.Price >= priceMin.Value))
                        ) &&
                        (
                            !priceMax.HasValue
                            ||
                            ((pv.SpecialPrice.HasValue && ((!pv.SpecialStartTime.HasValue || pv.SpecialStartTime.Value < nowUtc) && (!pv.SpecialEndTime.HasValue || pv.SpecialEndTime.Value > nowUtc))) && (pv.SpecialPrice <= priceMax.Value))
                            ||
                            ((!pv.SpecialPrice.HasValue || ((pv.SpecialStartTime.HasValue && pv.SpecialStartTime.Value > nowUtc) || (pv.SpecialEndTime.HasValue && pv.SpecialEndTime.Value < nowUtc))) && (pv.Price <= priceMax.Value))
                        )
                    select pv;

            if (filteredSpecs != null && filteredSpecs.Count > 0)
            {
                query = from p in query
                        from psa in _goodsParameterMappingRepository.Table.Where(a => a.AllowFiltering).Where(q => filteredSpecs.Contains(q.GoodsParameterOptionId))
                        select p;
            }
            if (categoryId != null && categoryId.Any() && categoryId != new int[1] { 0 })
            {
                query = from p in query
                        from pc in _goodsCategoryRepository.Table.Where(pc => categoryId.Contains(pc.CategoryId))
                        where p.Id == pc.GoodsId
                        select p;
            }
            if (brandId > 0)
                query = query.Where(q => q.BrandId == brandId);

            if (goodsTagId > 0)
            {
                query = from p in query
                        from pt in _goodsTagGoodsMappingRepository.Table.Where(pt => goodsTagId == pt.GoodsTagId && p.Id == pt.GoodsId)
                        select p;
            }

            query = from p in query
                    group p by p.Id
                        into pGroup
                    orderby pGroup.Key
                    select pGroup.FirstOrDefault();


            if (orderBy == GoodsSortingEnum.Position)
            {
                query = query.OrderByDescending(p => p.Id);
            }
            else if (orderBy == GoodsSortingEnum.NameAsc)
            {
                query = query.OrderBy(p => p.Name);
            }
            else if (orderBy == GoodsSortingEnum.NameDesc)
            {
                query = query.OrderByDescending(p => p.Name);
            }
            else if (orderBy == GoodsSortingEnum.PriceAsc)
            {
                query = query.OrderBy(p => p.Price);
            }
            else if (orderBy == GoodsSortingEnum.PriceDesc)
            {
                query = query.OrderByDescending(p => p.Price);
            }
            else if (orderBy == GoodsSortingEnum.CreateTime)
            {
                query = query.OrderByDescending(p => p.CreateTime);
            }
            else
            {
                query = query.OrderBy(p => p.Name);
            }

            var list = new PagedList<Goods>(query, pageIndex, pageSize);

            if (loadFilterableGoodsParameterOptionIds && false)
            {
                var querySpecs = from p in query
                                 join psa in _goodsParameterMappingRepository.Table on p.Id equals psa.GoodsId
                                 where psa.AllowFiltering
                                 select psa.GoodsParameterOptionId;
                filterableGoodsParameterOptionIds = querySpecs
                    .Distinct()
                    .ToList();
            }

            return list;

            #endregion

        }

        public virtual void UpdateGoodsReviewTotals(Goods goods)
        {
            if (goods == null)
                throw new ArgumentNullException("goods");

            int approvedRatingSum = 0;
            int approvedTotalReviews = 0;
            var reviews = _goodsReviewRepository.Table.Where(q => q.GoodsId == goods.Id).ToList();
            foreach (var pr in reviews)
            {
                approvedRatingSum += pr.Rating;
                approvedTotalReviews++;
            }

            goods.ApprovedRatingSum = approvedRatingSum;
            goods.ApprovedTotalReviews = approvedTotalReviews;
            UpdateGoods(goods);
        }

        public IPagedList<Goods> GetLowStockGoodss(int pageIndex = 0, int pageSize = int.MaxValue)
        {
            var query = from p in _goodsRepository.Table
                        orderby p.MinStockQuantity
                        where !p.Deleted &&
                        p.ManageStockEnabled &&
                        p.MinStockQuantity >= p.StockQuantity
                        select p;

            return new PagedList<Goods>(query, pageIndex, pageSize);
        }

        public virtual IPagedList<GoodsSpecCombination> GetLowStockGoodsCombinations(int pageIndex = 0, int pageSize = int.MaxValue)
        {
            var query = from p in _goodsRepository.Table
                        from c in p.GoodsSpecCombinations
                        orderby c.StockQuantity
                        where !p.Deleted &&
                        p.ManageStockEnabled &&
                        c.StockQuantity <= 0
                        select c;

            return new PagedList<GoodsSpecCombination>(query, pageIndex, pageSize);
        }


        public virtual Goods GetGoodsBySku(string sku)
        {
            if (String.IsNullOrEmpty(sku))
                return null;

            sku = sku.Trim();

            var query = from pv in _goodsRepository.Table
                        orderby pv.Id
                        where pv.Sku == sku
                        select pv;
            var goods = query.FirstOrDefault();
            return goods;
        }

        public virtual void AdjustInventory(Goods goods, bool decrease,
            int quantity, string attributesXml)
        {
            if (goods == null)
                throw new ArgumentNullException("goods");

            if (!goods.ManageStockEnabled) return;

            var prevStockQuantity = goods.StockQuantity;
            int newStockQuantity = 0;
            int saleVolume = 0;
            if (decrease)
            {
                newStockQuantity = goods.StockQuantity - quantity;
                saleVolume = goods.SaleVolume + quantity;
            }
            else
            {
                newStockQuantity = goods.StockQuantity + quantity;
                saleVolume = goods.SaleVolume - quantity;
            }
            goods.StockQuantity = newStockQuantity;
            goods.SaleVolume = saleVolume;

            if (decrease && goods.MinStockQuantity >= newStockQuantity)
            {
                goods.Published = false;
            }
            UpdateGoods(goods);

            if (!string.IsNullOrEmpty(attributesXml))
            {
                var combination = _goodsSpecParser.FindGoodsSpecCombination(goods, attributesXml);
                if (combination != null)
                {
                    int cQuantity = 0;
                    if (decrease)
                        cQuantity = combination.StockQuantity - quantity;
                    else
                        cQuantity = combination.StockQuantity + quantity;

                    combination.StockQuantity = cQuantity;
                    _goodsSpecService.UpdateGoodsSpecCombination(combination);
                }
            }
        }

        #endregion

        #region Goods pictures

        public virtual void DeleteGoodsPicture(GoodsPicture goodsPicture)
        {
            if (goodsPicture == null)
                throw new ArgumentNullException("goodsPicture");

            _goodsPictureRepository.Delete(goodsPicture);

        }

        public virtual IList<GoodsPicture> GetGoodsPicturesByGoodsId(int goodsId)
        {
            var query = from pp in _goodsPictureRepository.Table
                        where pp.GoodsId == goodsId
                        orderby pp.DisplayOrder
                        select pp;
            var goodsPictures = query.ToList();
            return goodsPictures;
        }

        public virtual GoodsPicture GetGoodsPictureById(int goodsPictureId)
        {
            if (goodsPictureId == 0)
                return null;

            var pp = _goodsPictureRepository.GetById(goodsPictureId);
            return pp;
        }

        public virtual void InsertGoodsPicture(GoodsPicture goodsPicture)
        {
            if (goodsPicture == null)
                throw new ArgumentNullException("goodsPicture");

            _goodsPictureRepository.Insert(goodsPicture);
        }

        public virtual void UpdateGoodsPicture(GoodsPicture goodsPicture)
        {
            if (goodsPicture == null)
                throw new ArgumentNullException("goodsPicture");

            _goodsPictureRepository.Update(goodsPicture);
        }

        #endregion

        #region Goods Reviews

        public void DeleteGoodsReview(GoodsReview goodsReview)
        {
            if (goodsReview == null)
                throw new ArgumentNullException("goodsReview");

            _goodsReviewRepository.Delete(goodsReview);

        }

        public IPagedList<GoodsReview> GetGoodsReviews(int? goodsId, int? orderId, int? userId, bool? isApproved = false,
            DateTime? startTime = null, DateTime? endTime = null, int pageIndex = 0, int pageSize = int.MaxValue)
        {
            var query = _goodsReviewRepository.Table;

            if (goodsId.HasValue && goodsId.Value > 0)
                query = query.Where(o => o.GoodsId == goodsId.Value);
            if (userId.HasValue)
                query = query.Where(o => o.UserId == userId.Value);
            if (orderId.HasValue)
                query = query.Where(o => o.OrderId == orderId.Value);
            if (startTime.HasValue)
                query = query.Where(o => startTime.Value <= o.CreateTime);
            if (endTime.HasValue)
                query = query.Where(o => endTime.Value >= o.CreateTime);

            query = query.OrderByDescending(c => c.CreateTime);
            var reviews = new PagedList<GoodsReview>(query, pageIndex, pageSize);
            return reviews;
        }

        public GoodsReview GetGoodsReview(int goodsId, int orderId)
        {
            var query = _goodsReviewRepository.Table;

            query = query.Where(o => o.GoodsId == goodsId && o.OrderId == orderId);

            return query.FirstOrDefault();
        }

        public GoodsReview GetGoodsReviewById(int goodsReviewId)
        {
            if (goodsReviewId == 0)
                return null;

            var pp = _goodsReviewRepository.GetById(goodsReviewId);
            return pp;
        }

        public void InsertGoodsReview(GoodsReview goodsReview)
        {
            if (goodsReview == null)
                throw new ArgumentNullException("goodsReview");

            _goodsReviewRepository.Insert(goodsReview);
        }

        public void UpdateGoodsReview(GoodsReview goodsReview)
        {
            if (goodsReview == null)
                throw new ArgumentNullException("goodsReview");

            _goodsReviewRepository.Update(goodsReview);
        }

        #endregion

        #region Goods Tag

        public IList<Goods> GetGoodsByTag(int goodsTagId, bool random = false, int count = 6)
        {
            var query = (from p in _goodsRepository.Table
                         join pt in _goodsTagGoodsMappingRepository.Table on p.Id equals pt.GoodsId
                         where p.Published && goodsTagId == pt.GoodsTagId &&
                         !p.Deleted
                         select p);

            var list = query.Take(count).ToList();
            return list;
        }


        #endregion

        #endregion

    }
}
